package com.dcits.business.server.redis;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.net.telnet.TelnetClient;
import org.apache.log4j.Logger;

import com.alibaba.fastjson.annotation.JSONField;
import com.dcits.business.server.ViewServerInfo;
import com.dcits.tool.RmpUtil;
import com.dcits.tool.StringUtils;
import com.jfinal.kit.PathKit;

public class RedisServer extends ViewServerInfo {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	public static final String SERVER_TYPE_NAME = "redis";
	
	private static final Logger logger = Logger.getLogger(RedisServer.class);
	private static final Map<String, Object> alertThreshold = new HashMap<String, Object>();

	public static final String redis_cli_path = PathKit.getWebRootPath() + File.separator + "tools" + File.separator + "redis";
	public static final String redis_cli_for_linux = redis_cli_path + File.separator + "redis-cli_linux64";
	public static final String redis_cli_for_window = redis_cli_path + File.separator + "redis-cli_win64.exe";
	
	public static String redis_cli_bin;
	
	public static final int connectTimeout = 5000;
	public static final int soTimeout = 10000;
	
	@JSONField(serialize=false)
	private TelnetClient client;
	@JSONField(serialize=false)
	private PrintStream out;
	@JSONField(serialize=false)
	private InputStream in;
	
	static {
		// TODO Auto-generated method stub
		alertThreshold.put("usedCpu", 3600);
		alertThreshold.put("keyspaceHitRateCurrent", 50);
		alertThreshold.put("totalPercentSecondCurrent", 5000000);
		
		//判断当前环境确定连接客户端和方法
		String os = System.getProperty("os.name");  
		if (os.toLowerCase().startsWith("win")) {
			redis_cli_bin = redis_cli_for_window;
		} else {
			redis_cli_bin = redis_cli_for_linux;
		}
	}

	public RedisServer() {
		// TODO Auto-generated constructor stub
		super(new RedisMonitoringInfo());
	}
	
	@Override
	public String connect() {
		// TODO Auto-generated method stub
		String command = redis_cli_bin + " " + "-h " + this.getHost() + " -p " + this.getPort() + " info";
		try {
			String str = RmpUtil.execCommand(command);
			if (str.contains("error") || str.contains("Could not")) {
				return "连接失败,请检查:" + str;
			}
			
			this.client = new TelnetClient();
			this.client.connect(this.getHost(), this.getPort());
			this.client.setConnectTimeout(connectTimeout);
			this.client.setSoTimeout(soTimeout);
			this.out = new PrintStream(this.client.getOutputStream());
			this.in = this.client.getInputStream();
			
			return "true";
		} catch (Exception e) {
			// TODO Auto-generated catch block
			logger.error("本地执行命令失败:" + command, e);
			return "执行命令失败[" + command + "]：" + e.getMessage();
		}
	}

	public String request(String requestInfo) throws IOException {
		StringBuilder replyMessage = new StringBuilder("");
		this.out.println(requestInfo);
		this.out.flush();
		try {
			char ch = (char) in.read(); 
			while (true) {  
				replyMessage.append(ch);
				 if (ch == '\t') {  
	                 if (replyMessage.toString().endsWith("\n")) {  
	                     return replyMessage.toString();
	                 }  
	             } 
				 ch = (char) in.read(); 
			}
		} catch (Exception e) {
			// TODO: handle exception
			logger.error("redis-telnet接收信息失败!", e);
		}
		return null;		  
	}
	
	@Override
	public boolean disconect() {
		// TODO Auto-generated method stub
		return true;
	}

	@Override
	public void getMonitoringInfo() {
		// TODO Auto-generated method stub		
		String command = redis_cli_bin + " " + "-h " + this.getHost() + " -p " + this.getPort() + " info";
		long currentTimestamp = System.currentTimeMillis();		
		try {
			String str = RmpUtil.execCommand(command);
			
			if (str != null && (str.contains("error") || str.contains("Could not"))) {
				this.connectStatus = "连接失败,请检查:" + str;
				return;
			} 			
			parseInfo(str, currentTimestamp);
		} catch (Exception e) {
			// TODO: handle exception
			this.connectStatus = "执行命令失败[" + command + "]：" + e.getMessage();
			logger.error("本地执行命令失败:" + command, e);
			
		}
	}
	
	/**
	 * 解析返回<br>
	 * # Server<br>
		redis_version:2.8.3<br>
		redis_git_sha1:00000000<br>
		redis_git_dirty:0<br>
		redis_build_id:70182195b0dd4310<br>
		redis_mode:standalone<br>
		os:Linux 3.10.0-693.11.1.el7.x86_64 x86_64<br>
		arch_bits:64<br>
		multiplexing_api:epoll<br>
		gcc_version:4.8.5<br>
		process_id:27720<br>
		run_id:c76658daed87145c22bb85f868167d5e419005e1<br>
		tcp_port:6379<br>
		uptime_in_seconds:17<br>
		uptime_in_days:0<br>
		hz:10<br>
		lru_clock:1789252<br>
		config_file:<br>
		<br>
		# Clients<br>
		connected_clients:1<br>
		client_longest_output_list:0<br>
		client_biggest_input_buf:0<br>
		blocked_clients:0<br>
		<br>
		# Memory<br>
		used_memory:809176<br>
		used_memory_human:790.21K<br>
		used_memory_rss:2322432<br>
		used_memory_peak:750528<br>
		used_memory_peak_human:732.94K<br>
		used_memory_lua:33792<br>
		mem_fragmentation_ratio:2.87<br>
		mem_allocator:jemalloc-3.2.0<br>
		<br>
		# Persistence<br>
		loading:0<br>
		rdb_changes_since_last_save:0<br>
		rdb_bgsave_in_progress:0<br>
		rdb_last_save_time:1527841946<br>
		rdb_last_bgsave_status:ok<br>
		rdb_last_bgsave_time_sec:-1<br>
		rdb_current_bgsave_time_sec:-1<br>
		aof_enabled:0<br>
		aof_rewrite_in_progress:0<br>
		aof_rewrite_scheduled:0<br>
		aof_last_rewrite_time_sec:-1<br>
		aof_current_rewrite_time_sec:-1<br>
		aof_last_bgrewrite_status:ok<br>
		<br>
		# Stats<br>
		total_connections_received:1<br>
		total_commands_processed:0<br>
		instantaneous_ops_per_sec:0<br>
		rejected_connections:0<br>
		sync_full:0<br>
		sync_partial_ok:0<br>
		sync_partial_err:0<br>
		expired_keys:0<br>
		evicted_keys:0<br>
		keyspace_hits:0<br>
		keyspace_misses:0<br>
		pubsub_channels:0<br>
		pubsub_patterns:0<br>
		latest_fork_usec:0<br>
		<br>
		# Replication<br>
		role:master<br>
		connected_slaves:0<br>
		master_repl_offset:0<br>
		repl_backlog_active:0<br>
		repl_backlog_size:1048576<br>
		repl_backlog_first_byte_offset:0<br>
		repl_backlog_histlen:0<br>
		<br>
		# CPU<br>
		used_cpu_sys:0.00<br>
		used_cpu_user:0.00<br>
		used_cpu_sys_children:0.00<br>
		used_cpu_user_children:0.00<br>
		<br>
		# Keyspace<br>

	 * @param info
	 */
	public void parseInfo(String info, long currentTimestamp) {
		RedisMonitoringInfo monitoringInfo = (RedisMonitoringInfo) this.getInfo();	
		if (StringUtils.isNotEmpty(info)) {
			//将信息解析成Map
			Map<String, String> infos = new HashMap<String, String>();
			for (String s:info.split("\\n+")) {
				if (StringUtils.isEmpty(s) || s.trim().startsWith("#")) {
					continue;
				}
			
				String[] spliteSS = s.trim().split(":");
				if (spliteSS.length == 2) infos.put(spliteSS[0], spliteSS[1]);			
			}
			//获取指定内容
			monitoringInfo.setVersion(infos.get("redis_version"));
			monitoringInfo.setProcessId(infos.get("process_id"));
			monitoringInfo.setUpTime(infos.get("uptime_in_days"));
			monitoringInfo.setClientCount(infos.get("connected_clients"));
			monitoringInfo.setUsedMemory(String.valueOf(Long.valueOf(infos.get("used_memory")) / 1000));
			monitoringInfo.setUsedMemoryMax(String.valueOf(Long.valueOf(infos.get("used_memory_peak")) / 1000));
			monitoringInfo.setUsedCpuSys(infos.get("used_cpu_sys"));
			monitoringInfo.setUsedCpuUser(infos.get("used_cpu_user"));
			monitoringInfo.setUsedCpu(String.format("%.2f", Double.valueOf(infos.get("used_cpu_sys")) + Double.valueOf(infos.get("used_cpu_user"))));
			if (monitoringInfo.getTimestamp() != 0L) {
				double a = (Double.valueOf(infos.get("total_commands_processed")) - Double.valueOf(monitoringInfo.getTotalCommandCount()) - 1) / (((double)currentTimestamp - monitoringInfo.getTimestamp()) / 1000) ;
				monitoringInfo.setTotalPercentSecondCurrent(String.format("%.2f", a));
			}			
			monitoringInfo.setTotalCommandCount(infos.get("total_commands_processed"));						
			//计算实时命中率
			if (monitoringInfo.getKeyspaceHits() != null && monitoringInfo.getKeyspaceMisses() != null) {
				//(keyspaceHits2 - keyspaceHits1) / ((keyspaceHits2 - keyspaceHits1) + (keyspaceMisses2 - keyspaceMisses1)) * 100
				long hitCount = Long.valueOf(infos.get("keyspace_hits")) - Long.valueOf(monitoringInfo.getKeyspaceHits());
				long missesCount = Long.valueOf(infos.get("keyspace_misses")) - Long.valueOf(monitoringInfo.getKeyspaceMisses());
				if ((hitCount + missesCount) > 0L) {
					monitoringInfo.setKeyspaceHitRateCurrent(String.format("%.2f", (double)hitCount / (hitCount + missesCount) * 100));
				} else {
					monitoringInfo.setKeyspaceHitRateCurrent("0.00");
				};
			}
			monitoringInfo.setKeyspaceHits(infos.get("keyspace_hits"));
			monitoringInfo.setKeyspaceMisses(infos.get("keyspace_misses"));
			monitoringInfo.setExpiredKeys(infos.get("expired_keys"));
			monitoringInfo.setEvictedKeys(infos.get("evicted_keys"));
			monitoringInfo.setRole(infos.get("role"));
			monitoringInfo.setTimestamp(currentTimestamp);
			
			//获取每个数据库的当前存在的key数量
			long dbKeyCount = 0L;
			//db0:keys=7,expires=0,avg_ttl=0
			for (int count = 0;count < 10000;count++) {
				if (infos.get("db" + count) == null) {
					break;
				}
				
				String s = infos.get("db" + count).trim().split(",")[0].split("=")[1];
				dbKeyCount += Long.valueOf(s);
			}
			monitoringInfo.setDbKeyCount(String.valueOf(dbKeyCount));
			monitoringInfo.setTime(new Date());
			this.connectStatus = "true";
		} else {
			this.connectStatus = "获取信息失败!";
		}
	}

}
